import copy

import uuid
import math

import cv2
from PIL import Image, ImageFont, ImageDraw
import aggdraw
import numpy as np

from src.dicom_viewer_be_mock_xiaosai.service.api_middleware import logger
from src.dicom_viewer_be_mock_xiaosai.components import VolumeInfo
from src.dicom_viewer_be_mock_xiaosai.type import EnumShape, EnumMeasureStatus, EnumMouseOp
from src.dicom_viewer_be_mock_xiaosai.utils.util import check_is_error_draw, HitCheck, calc_vector_angle

r_color = (255, 0, 0)
g_color = (0, 255, 0)
b_color = (0, 0, 255)
endpoint_ball_radius = 5
inactive_font_draw = ImageFont.truetype('arial.ttf', size=14)
active_font_draw = ImageFont.truetype('arial.ttf', size=15)
every_line_offset = 20

inactive_text_color = (255, 0, 255)
active_text_color = (0, 128, 0)

inactive_pen = aggdraw.Pen((255, 0, 255), 2, opacity=168)
active_pen = aggdraw.Pen('green', 3, opacity= 255)

Inactive_Status = 'inactive'
Active_Status = 'active'


class Draw:
    @staticmethod
    def draw_multi_lines_text(drawer, desc, start_point, which_pen):
        if desc is None or len(desc) == 0:
            return
        ans = []
        for index, item in enumerate(desc):
            line = ''
            if type(item) == dict:
                key = item['attr']
                value = item['value']
                line = f'{key}: {value}'
            elif type(item) == list:
                for index1, item1 in enumerate(item):
                    key1 = item1['attr']
                    value1 = item1['value']
                    line = line + f' {key1}: {value1}'
            ans.append(line)
        for index1, line1 in enumerate(ans):
            current_posi = (int(start_point[0]), int(start_point[1] - every_line_offset * index1))
            Draw.draw_text(drawer, line1, current_posi, which_pen)

    @staticmethod
    def draw_line(drawer, start, end, which_pen=Inactive_Status, color=r_color):
        if which_pen == Inactive_Status:
            drawer.line((int(start[0]), int(start[1]), int(end[0]), int(end[1])), inactive_pen)
        else:
            drawer.line((int(start[0]), int(start[1]), int(end[0]), int(end[1])), active_pen)

    @staticmethod
    def draw_circle(drawer, center_x, center_y, which_pen=Inactive_Status):
        bounding = (center_x - endpoint_ball_radius, center_y - endpoint_ball_radius, center_x + endpoint_ball_radius,
                    center_y + endpoint_ball_radius)
        Draw.draw_ellipse(drawer, bounding, which_pen)

    @staticmethod
    def draw_ellipse(drawer, bounding, which_pen):
        if which_pen == Inactive_Status:
            drawer.ellipse(bounding, inactive_pen)
        else:
            drawer.ellipse(bounding, active_pen)

    @staticmethod
    def draw_rect():
        pass

    @staticmethod
    def draw_text(drawer, content, posi, which_pen):
        if content is None:
            return
        if which_pen == Inactive_Status:
            font = inactive_font_draw
            drawer.text(posi, content, font=font, fill=inactive_text_color)
        else:
            font = active_font_draw
            drawer.text(posi, content, font=font, fill=active_text_color)


class MeasureRecord:
    def __init__(self, measure_type: EnumShape):
        target_points_count = 0
        match measure_type:
            case EnumShape.line.value:
                target_points_count = 2
            case EnumShape.angle.value:
                target_points_count = 3
            case EnumShape.rect.value | EnumShape.ellipse.value:
                target_points_count = 4
        self.id = str(uuid.uuid1())
        self.status: EnumMeasureStatus.value = EnumMeasureStatus.create.value
        self.points = []  # dicom 原始坐标下的点。
        self.measure_type: EnumShape = measure_type
        self.target_points_count = target_points_count
        self.desc = None
        # 正在编辑的点的索引。 -2是没有正在编辑的点， -1 为整体编辑
        # 0~为正在编辑的索引, 如果索引 >= target_points_count。其实是编辑完成了.
        self.editing_point_index = 0
        self.screen_points = []  # 在屏幕下的点的坐标。

    def update_latest_point(self, point):
        if len(self.points) == 0:
            return
        if self.editing_point_index == -2:
            return
        match self.measure_type:
            case EnumShape.rect.value | EnumShape.ellipse.value:
                self.points[self.editing_point_index] = point
                x, y = point
                match self.editing_point_index:
                    case 0:
                        self.points[1] = (self.points[1][0], y)
                        self.points[3] = (x, self.points[3][1])

                    case 1:
                        self.points[0] = (self.points[0][0], y)
                        self.points[2] = (x, self.points[2][1])

                    case 2:
                        self.points[1] = (x, self.points[1][1])
                        self.points[3] = (self.points[3][0], y)

                    case 3:
                        self.points[0] = (x, self.points[0][1])
                        self.points[2] = (self.points[2][0], y)
            case _:
                self.points[self.editing_point_index] = point

    def confirm_a_point(self, point):
        match self.measure_type:
            case EnumShape.rect.value | EnumShape.ellipse.value:
                if len(self.points) == 0:
                    self.points = [] + [point, point, point, point]
                    self.editing_point_index = 2
            case EnumShape.line.value | EnumShape.angle.value:
                    if len(self.points) == 0:
                        self.points.append(point)
                        self.points.append(point)
                    else:
                        self.points.append(point)
                    self.editing_point_index = self.editing_point_index + 1
            case _:
                logger.error('error measure type')

    def check_measure_status(self):
        finished = False
        has_error = False
        if self.status == EnumMeasureStatus.selected.value or len(self.points) < self.target_points_count:
            return finished, has_error
        finished = True
        has_error, reason = check_is_error_draw(self)
        if reason is not None:
            logger.info(reason)
        return finished, has_error


# key 是当前所在Index值， value是 measureId数组
class MeasureDoneDict:
    def __init__(self):
        self.dict = dict()

    def append_a_record(self, key, record):
        if self.dict.__contains__(key):
            arr = self.dict.get(key)
            arr.append(record)
        else:
            self.dict.setdefault(key, [record])

    def remove_a_record(self, key, record_id):
        if self.dict.__contains__(key):
            arr = self.dict.get(key)
            arr.remove(record_id)


class NewMeasureLayer:
    def __init__(self, view_type, dicom_tag_info, volume_info):
        self.view_type = view_type
        self.dicom_tag_info = dicom_tag_info
        self.volume_info: VolumeInfo = volume_info
        self.drawer = None
        self.text_drawer = None
        self.draw_image = None

        self.size = (0, 0)  # h,w
        # measure的信息，已经完成的, measureId => measureRecord
        self.measure_done_dict = dict()
        self.measuring: None | MeasureRecord = None
        # key 是当前影像所在的norm_index, val 是 [measureId]
        self.norm_index_done_dict = MeasureDoneDict()

    def clear_measure(self):
        self.measure_done_dict = dict()
        self.measuring = None
        self.norm_index_done_dict = MeasureDoneDict()

    def set_layer_size(self):
        render_size = self.volume_info.view_info[self.view_type].render_size
        if self.size is None:
            self.size = (render_size[0], render_size[1])
        elif self.size != (render_size[0], render_size[1]):
            # resize的情况。
            logger.info('resize, reset measure draw_image')
            self.size = (render_size[0], render_size[1])
            self.draw_image = None

    def init_layer(self):
        self.set_layer_size()
        height, width = self.size
        # 如果图层已存在，不用每次重新创建
        if self.draw_image is None:
            self.draw_image = Image.new('RGBA', (width, height), (0, 0, 0, 0))
        else:
            self.draw_image.paste((0, 0, 0, 0), (0, 0,  width, height))
        self.drawer = aggdraw.Draw(self.draw_image)
        self.text_drawer = ImageDraw.Draw(self.draw_image)

    def get_layer(self):
        self.init_layer()
        self.draw_all_records()
        exist_measure_flag = self.check_exist_measure()
        if exist_measure_flag:
            return self.draw_image
        return None

    def check_exist_measure(self):
        if self.measuring is not None:
            return True
        done_id_arr = self.get_current_done_measure_ids()
        return len(done_id_arr) != 0

    def create_measuring(self, measure_type: EnumShape):
        self.measuring = MeasureRecord(measure_type=measure_type)

    def draw_all_records(self):
        self.draw_measure_done()
        self.draw_measuring()
        self.drawer.flush()

        self.draw_measure_done_desc()
        self.draw_measuring_desc()


    def draw_measure_done_desc(self):
        current_norm_index = self.get_current_norm_index()
        measure_done_id_arr = self.norm_index_done_dict.dict.get(current_norm_index)
        if measure_done_id_arr is None:
            return
        for id_done in measure_done_id_arr:
            measure_record = self.measure_done_dict.get(id_done)
            self.draw_a_measure_desc(measure_record, Inactive_Status)

    def draw_measuring_desc(self):
        if self.measuring is None:
            return
        self.draw_a_measure_desc(self.measuring, Active_Status)

    def draw_measure_done(self):
        current_norm_index = self.get_current_norm_index()
        measure_done_id_arr = self.norm_index_done_dict.dict.get(current_norm_index)
        if measure_done_id_arr is None:
            return
        for id_done in measure_done_id_arr:
            measure_record = self.measure_done_dict.get(id_done)
            self.draw_a_measure_record(measure_record, Inactive_Status)

    def draw_measuring(self):
        if self.measuring is None:
            return
        desc = self.get_desc_by_shape(self.measuring)
        if desc is None:
            return
        self.measuring.desc = desc
        self.draw_a_measure_record(self.measuring, Active_Status)

    def draw_a_measure_record(self, measure_record: MeasureRecord, pen_status):
        points = measure_record.points
        status = measure_record.status
        measure_type = measure_record.measure_type
        draw_circle_flag = [EnumMeasureStatus.create.value, EnumMeasureStatus.selected.value].__contains__(status)
        screen_points = [self.dicom_2_screen_position(each_point) for each_point in points]
        measure_record.screen_points = screen_points
        match measure_type:
            case EnumShape.ellipse.value:
                p0 = screen_points[0]
                p2 = screen_points[2]
                left = (min(p0[0], p2[0]), min(p0[1], p2[1]))
                right = (max(p0[0], p2[0]), max(p0[1], p2[1]))
                Draw.draw_ellipse(self.drawer, left + right, pen_status)
                for index, point in enumerate(screen_points):
                    if draw_circle_flag:
                        Draw.draw_circle(self.drawer, point[0], point[1], pen_status)

            case _:
                for index, point in enumerate(screen_points):
                    if draw_circle_flag:
                        Draw.draw_circle(self.drawer, point[0], point[1], pen_status)
                    if index != len(screen_points) - 1:
                        next_point = screen_points[index + 1]
                        Draw.draw_line(self.drawer, point, next_point, pen_status)
                    elif index == len(screen_points) - 1 and measure_record.measure_type == EnumShape.rect.value:
                        next_point = screen_points[0]
                        Draw.draw_line(self.drawer, point, next_point, pen_status)


    def draw_a_measure_desc(self, measure_record: MeasureRecord, status):
        measure_type = measure_record.measure_type
        desc = measure_record.desc
        if desc is None:
            return
        screen_points = measure_record.screen_points
        if len(screen_points) < 2:
            return
        turn_point = screen_points[1]
        match measure_type:
            case EnumShape.ellipse.value | EnumShape.rect.value:
                Draw.draw_multi_lines_text(self.text_drawer, desc, turn_point, status)
            case _:
                Draw.draw_text(self.text_drawer, desc, turn_point, status)

    def handle_mouse_down(self, measure_type: EnumShape, position):
        if self.measuring is not None and self.measuring.status == EnumMeasureStatus.create.value:
            self.confirm_a_point(measure_type, position)
            return

        # 1. 如果当前有selected状态的measure，优先判断是否命中该状态下的点。
        # 2. 如果没有命中。 判断是否命中某个measure
        if self.measuring is not None and self.measuring.status == EnumMeasureStatus.selected.value:
            editing_index = HitCheck.hit_point(position, self.measuring.points)
            self.measuring.editing_point_index = editing_index
            if editing_index != -2:
                logger.info(f'命中了控制点 {editing_index}')
                return
            else:
                self.move_measuring_to_done()
        hit_flag, hit_measure_id = self.check_down_in_which_shape(position)
        if hit_flag is False:
            self.confirm_a_point(measure_type, position)

    def check_down_in_which_shape(self, click_point):
        # 先检查是否命中正在绘制中的。
        # 再检查是否命中已经完成得绘制。
        flag = False
        hit_measure_id = None

        if self.measuring is not None and len(self.measuring.points) == self.measuring.target_points_count:
            points = self.measuring.points
            measure_type = self.measuring.measure_type
            flag = HitCheck.hit_line(click_point, measure_type, points)
            hit_measure_id = self.measuring.id

        if flag is False:
            measure_done_ids = self.get_current_done_measure_ids()
            for measure_done_id in measure_done_ids:
                record: MeasureRecord = self.measure_done_dict.get(measure_done_id)
                points = record.points
                measure_type = record.measure_type
                flag = HitCheck.hit_line(click_point, measure_type, points)
                if flag:
                    hit_measure_id = record.id
                    # 点击的在已有图形中，将点中的图像加入正在绘制中的。
                    if self.measuring is not None and self.measuring.id != hit_measure_id:
                        self.move_measuring_to_done()
                    self.move_measured_to_measuring(measure_done_id)
                    break
        return flag, hit_measure_id

    def move_measured_to_measuring(self, measure_id):
        record: MeasureRecord = self.measure_done_dict.get(measure_id)
        self.measuring = copy.deepcopy(record)
        self.measuring.status = EnumMeasureStatus.selected.value
        self.measure_done_dict.pop(measure_id)
        current_norm_index = self.get_current_norm_index()
        self.norm_index_done_dict.remove_a_record(current_norm_index, measure_id)

    def handle_mouse_op(self, mouse_op: EnumMouseOp, screen_position, measure_type: EnumShape):
        origin_point = self.screen_coord_to_origin_voxel(screen_position)
        match mouse_op:
            case EnumMouseOp.down.value:
                logger.info('down')
                self.handle_mouse_down(measure_type, origin_point)
            case EnumMouseOp.move.value:
                pass
            case EnumMouseOp.drag.value:
                logger.info('drag')
                if self.measuring is not None:
                    self.handle_drag(measure_type, origin_point)
            case EnumMouseOp.up.value:
                logger.info('up')
                if self.measuring is not None:
                    self.check_measuring_finish()

    def screen_coord_to_origin_voxel(self, point):
        display_y = point[1]
        display_x = point[0]
        original_point = self.volume_info.screen_coord_to_origin_voxel(self.view_type, display_y, display_x)
        return round(original_point[1]), round(original_point[0])

    def dicom_2_screen_position(self, point):
        screen_position = self.volume_info.origin_voxel_coord_to_screen_coord(self.view_type, point)
        return screen_position

    def confirm_a_point(self, measure_type, point):
        if self.measuring is None:
            self.create_measuring(measure_type=measure_type)

        match self.measuring.status:
            case EnumMeasureStatus.create.value:
                self.measuring.confirm_a_point(point)

    def check_measuring_finish(self):
        finished, has_error = self.measuring.check_measure_status()
        if finished is False:
            return
        if has_error:
            self.clear_measuring()
        else:
            self.move_measuring_to_done()

    def get_current_norm_index(self):
        return self.volume_info.get_current_slice_index(self.view_type)

    def move_measuring_to_done(self):
        measuring = copy.deepcopy(self.measuring)
        measuring.status = EnumMeasureStatus.done.value
        self.measure_done_dict.setdefault(measuring.id, measuring)
        current_norm_index = self.get_current_norm_index()
        self.norm_index_done_dict.append_a_record(current_norm_index, measuring.id)
        self.clear_measuring()

    # 获取当前slice的所有已经绘制的图像
    def get_current_done_measure_ids(self):
        current_norm_index = self.get_current_norm_index()
        current_norm_index_done = self.norm_index_done_dict.dict.get(current_norm_index)
        if current_norm_index_done is None:
            return []
        else:
            return current_norm_index_done

    def clear_measuring(self):
        self.measuring = None

    def handle_drag(self, measure_type, point):
        self.measuring.update_latest_point(point)


    def get_desc_by_shape(self, measure_record: MeasureRecord):
        shape = measure_record.measure_type
        points = measure_record.points
        desc = ''
        match shape:
            case EnumShape.ellipse.value:
                hu_array = self.volume_info.get_current_dicom_data(self.view_type)
                spacing = self.volume_info.get_spacing(self.view_type)
                x_mm_per_px, y_mm_per_px = spacing

                p0 = points[0]
                p2 = points[2]

                # 假设椭圆的参数：中心 (cx, cy), 长半轴 a, 短半轴 b
                cx, cy = round((p0[0] + p2[0]) / 2), round((p0[1] + p2[1]) / 2)  # 椭圆中心
                a, b = round(abs(p0[0] - p2[0]) * x_mm_per_px / 2), round(
                    abs(p0[1] - p2[1]) * y_mm_per_px / 2)  # 长半轴和短半轴的长度

                area = round(np.pi * a * b)
                mask = np.zeros_like(hu_array, dtype=np.uint8)
                cv2.ellipse(mask, (cx, cy), (a, b), 0, 0, 360, 1, thickness=-1)
                pixels_in_roi = hu_array[mask == 1]
                if area < 10 or len(pixels_in_roi) <= 1:
                    return None

                mean_val = np.mean(pixels_in_roi)
                stddev_val = np.std(pixels_in_roi)
                max_val = np.max(pixels_in_roi)
                min_val = np.min(pixels_in_roi)

                mean = round(mean_val, 2)
                min_val = round(min_val, 2)
                max_val = round(max_val, 2)
                stddev = round(stddev_val, 2)

                area_str = f'{area / 100} cm²'
                w_str = f'{a * 2} mm'
                h_str = f'{b * 2} mm'
                ans = [
                    {
                        'attr': 'Mean',
                        'value': mean
                    },
                    {
                        'attr': 'Min',
                        'value': min_val
                    },
                    {
                        'attr': 'Max',
                        'value': max_val
                    },
                    {
                        'attr': 'StdDev',
                        'value': stddev
                    },
                    {
                        'attr': 'Area',
                        'value': area_str
                    },
                    [
                        {
                            'attr': 'W',
                            'value': w_str
                        },
                        {
                            'attr': 'H',
                            'value': h_str
                        },
                    ]
                ]
                return ans
            case EnumShape.rect.value:
                x1, y1 = points[0]
                x2, y2 = points[2]
                if y1 < y2:
                    y_from = y1
                    y_to = y2
                else:
                    y_from = y2
                    y_to = y1

                if x1 < x2:
                    x_from = x1
                    x_to = x2
                else:
                    x_from = x2
                    x_to = x1

                hu_array = self.volume_info.get_current_dicom_data(self.view_type)
                ct_values = np.array(hu_array)
                h, w = ct_values.shape
                padding_top = max(0, -y_from)  # 如果 y_from 为负，需在顶部填充
                padding_left = max(0, -x_from)  # 如果 x_from 为负，需在左侧填充
                padding_right = max(0, x_to - w)
                padding_bottom = max(0, y_to - h)

                y_from = max(0, y_from)
                y_to = max(0, y_to) + padding_top
                x_from = max(0, x_from)
                x_to = max(0, x_to) + padding_left

                padding_ct_values = np.pad(ct_values, ((padding_top, padding_bottom), (padding_left, padding_right)),
                                           mode='constant',
                                           constant_values=-1000)

                region_ct_values = padding_ct_values[y_from:y_to, x_from:x_to]
                spacing = self.volume_info.get_spacing(self.view_type)
                x_mm_per_px, y_mm_per_px = spacing
                # 计算宽度 W 和高度 H
                W = round(region_ct_values.shape[1] * x_mm_per_px, 2)  # 矩形的宽度
                H = round(region_ct_values.shape[0] * y_mm_per_px, 2)  # 矩形的高度
                if W * H < 10:
                    return None

                # 计算统计数据
                mean_val = np.mean(region_ct_values)
                stddev_val = np.std(region_ct_values)
                max_val = np.max(region_ct_values)
                min_val = np.min(region_ct_values)

                mean = round(mean_val, 2)
                min_val = round(min_val)
                max_val = round(max_val)
                stddev = round(stddev_val, 2)

                area = round(W * H / 100, 2)

                area_str = f'{area} cm²'
                w_str = f'{W} mm'
                h_str = f'{H} mm'
                ans = [
                    {
                        'attr': 'Mean',
                        'value': mean
                    },
                    {
                        'attr': 'Min',
                        'value': min_val
                    },
                    {
                        'attr': 'Max',
                        'value': max_val
                    },
                    {
                        'attr': 'StdDev',
                        'value': stddev
                    },
                    {
                        'attr': 'Area',
                        'value': area_str
                    },
                    [
                        {
                            'attr': 'W',
                            'value': w_str
                        },
                        {
                            'attr': 'H',
                            'value': h_str
                        },
                    ]
                ]
                return ans
            case EnumShape.line.value:
                dis = self.get_distance(points[0], points[1])
                desc = f'{dis} mm'
            case EnumShape.angle.value:
                angle = self.get_angle(points)
                desc = f'{angle} °'
        return desc

    def get_distance(self, point0, point1):
        delta_x = abs(point0[0] - point1[0])
        delta_y = abs(point0[1] - point1[1])
        spacing = self.volume_info.get_spacing(self.view_type)
        x_mm_per_px, y_mm_per_px = spacing
        distance = math.sqrt((delta_x / x_mm_per_px) ** 2 + (delta_y / y_mm_per_px) ** 2)
        return round(distance / 10, 2)

    def get_angle(self, points):
        if len(points) != 3:
            return ''
        vector1 = tuple(map(lambda x, y: x - y, points[0], points[1]))
        vector2 = tuple(map(lambda x, y: x - y, points[2], points[1]))
        spacing = self.volume_info.get_spacing(self.view_type)
        actual_vector1 = tuple(vector1[i] * spacing[i] for i in range(len(vector1)))
        actual_vector2 = tuple(vector2[i] * spacing[i] for i in range(len(vector1)))
        return str(calc_vector_angle(actual_vector1, actual_vector2))
